#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
__author__ = 'Justin'
__mtime__ = '2018-10-13'

"""

import numpy as np
import ctypes
from ctypes.wintypes import LPARAM
from ctypes import c_char_p, c_int, c_float, c_double, c_bool, c_ubyte
from ctypes import POINTER, byref
from PIL import Image, ImageFile
import io, os
import sys
import yaml
import xml.dom.minidom
from skimage import draw
from skimage import color, morphology
from skimage.morphology import square

# 解除图片大小限制
ImageFile.LOAD_TRUNCATED_IMAGES = True
Image.MAX_IMAGE_PIXELS = None

# 定义结构体
class ImageInfoStruct(ctypes.Structure):
    _fields_ = [("DataFilePTR", LPARAM)]

yaml_path = os.path.dirname(os.path.realpath(__file__))+r'/kfb.yml'
config_sdk = yaml.safe_load(open(yaml_path))

class KFB_Slide(object):
    def __init__(self, filename):
        '''
        初始化过程
        :param filename: 切片文件所在路径
        '''
        self.filename = filename
        if sys.platform.startswith('win32'):
            # windows环境下
            KFB_SDK_PATH = config_sdk["KFB_SDK_PATH"]["win32"]#"C:/Work/KFB_SDK"
            self._Objdll_ = ctypes.windll.LoadLibrary(KFB_SDK_PATH + "/x64/ImageOperationLib")
        elif sys.platform.startswith('linux'):
            # Linux 环境下
            KFB_SDK_PATH = config_sdk["KFB_SDK_PATH"]["linux"]# "/home/whut/KFB_SDK"
            self._Objdll_ = ctypes.cdll.LoadLibrary(KFB_SDK_PATH + "/x64/libImageOperationLib.so")
        else:
            raise NotImplementedError
        '''
        bool InitImageFileFunc( ImageInfoStruct& sImageInfo, constchar* Path );
        参数：
        1.sImageInfo：返回数字图像文件指针
        2.Path ：数字图像路径
        '''
        # 定义返回类型和参数类型
        self.InitImageFileFunc = self._Objdll_.InitImageFileFunc
        self.InitImageFileFunc.restype = c_bool
        self.InitImageFileFunc.argtypes = [POINTER(ImageInfoStruct), c_char_p]

        '''
        bool GetHeaderInfoFunc( ImageInfoStruct sImageInfo, int&khiImageHeight,
              int	&khiImageWidth,int	&khiScanScale,float	&khiSpendTime,
              double	&khiScanTime,float	&khiImageCapRes,int&khiImageBlockSize);

        1.sImageInfo：传入图像数据指针
        2.khiImageHeight：返回扫描高度
        3.khiImageWidth：返回扫描宽度
        4.khiScanScale：返回扫描倍率
        5.khiSpendTime：返回扫描时间
        6.khiScanTime:返回扫描时间
        7.khiImageCapRes:返回扫描像素与um的比例
        8.khiImageBlockSize:返回扫描块大小
        '''
        self.GetHeaderInfoFunc = self._Objdll_.GetHeaderInfoFunc
        # print("GetHeaderInfoFunc ", self._Objdll_.GetHeaderInfoFunc)
        self.GetHeaderInfoFunc.restype = ctypes.c_bool
        self.GetHeaderInfoFunc.argtypes = [ImageInfoStruct, POINTER(c_int), POINTER(c_int),
                                           POINTER(c_int), POINTER(c_float), POINTER(c_double),
                                           POINTER(c_float), POINTER(c_int)]

        '''
        bool UnInitImageFileFunc( ImageInfoStruct& sImageInfo );

               参数：
               1.sImageInfo ： 传入数字图像文件指针
        '''
        self.UnInitImageFileFunc = self._Objdll_.UnInitImageFileFunc
        self.UnInitImageFileFunc.restype = c_bool
        self.UnInitImageFileFunc.argtypes = [POINTER(ImageInfoStruct)]

        '''
        bool GetImageDataRoiFunc( ImageInfoStruct sImageInfo, float fScale,
        int sp_x, int sp_y, int nWidth, int nHeight,
        BYTE** pBuffer, int&DataLength, bool flag);

        参数：
        1.	sImageInfo：传入图像数据指针
        2.	fScale：传入倍率
        3.	sp_x：左上角X坐标
        4.	sp_y：右上角Y坐标
        5.	nWidth：宽度
        6.	nHeight：高度
        7.	pBuffer：返回图像数据指针
        8.	DataLength：返回图像字节长度
        9.	flag：true

        '''
        self.GetImageDataRoiFunc = self._Objdll_.GetImageDataRoiFunc
        # print("GetImageDataRoiFunc ", self._Objdll_.GetImageDataRoiFunc)
        self.GetImageDataRoiFunc.restype = c_bool
        self.GetImageDataRoiFunc.argtypes = [ImageInfoStruct, c_float, c_int, c_int, c_int, c_int,
                                             POINTER(POINTER(c_ubyte)), POINTER(c_int), c_bool]

        self.img_pointer = None
        self.khiImageHeight = 0
        self.khiImageWidth = 0
        self.khiScanScale = 0
        self.m_id = ""

    # def __del__(self):
    #     if self.img_pointer :
    #         self.img_pointer = None
    #     self = None
    #     return

    def get_slide_pointer(self):
        '''
        得到切片指针
        :param filename: 切片文件路径
        :return:
        '''
        self.img_pointer = ImageInfoStruct()
        path_buf = ctypes.c_char_p(self.filename.encode())  # byte array

        return self.InitImageFileFunc(byref(self.img_pointer), path_buf)

    def get_header_info(self):
        '''
        读文件的头信息
        :return: bool 是否打开
        '''
        khiImageHeight = c_int()
        khiImageWidth = c_int()
        khiScanScale = c_int()
        khiSpendTime = c_float()
        khiScanTime = c_double()
        khiImageCapRes = c_float()
        khiImageBlockSize = c_int()

        success = self.GetHeaderInfoFunc(self.img_pointer, byref(khiImageHeight), byref(khiImageWidth),
                                         byref(khiScanScale),
                                         byref(khiSpendTime), byref(khiScanTime), byref(khiImageCapRes),
                                         byref(khiImageBlockSize))

        self.khiImageHeight = khiImageHeight.value
        self.khiImageWidth = khiImageWidth.value
        self.khiScanScale = khiScanScale.value

        return success

    def open_slide(self):
        '''
        打开切片文件
        :param filename: 切片文件
        :return: 是否成功打开
        '''
        tag = self.get_slide_pointer()
        if tag:
            return self.get_header_info()
        return False

    def get_image_width_height_byScale(self, scale):
        '''
        得到指定倍镜下，整个切片图像的大小
        :param scale: 提取图像所在的倍镜数
        :return: 图像的大小，宽（x）高（y）
        '''
        if self.khiScanScale == 0:
            return 0, 0

        ImageHeight = np.rint(self.khiImageHeight * scale / self.khiScanScale).astype(np.int64)
        ImageWidth = np.rint(self.khiImageWidth * scale / self.khiScanScale).astype(np.int64)
        return ImageWidth, ImageHeight

    def release_slide_pointer(self):
        # if self.img_pointer :
        return self.UnInitImageFileFunc(byref(self.img_pointer))

    @property
    def level_dimensions(self):
        '''
        得到对应不同下采样级数的（宽，高）元组（匹配openslide方法）
        '''
        ranks = 8  # 设置8个下采样级数，每级2倍下采样
        return tuple((self.khiImageWidth // 2**i, self.khiImageHeight // 2**i) for i in range(ranks))

    @property
    def level_downsamples(self):
        '''
        得到不同下采样级数下的下采样率（匹配openslide方法）
        '''
        return tuple(int((self.khiImageWidth / w + self.khiImageHeight / h) / 2) for (w, h) in self.level_dimensions)

    # def get_best_level_for_downsample(self, downsample):
    #     """Return the best level for displaying the given downsample."""
    #     for i, rate in enumerate(self.level_downsamples):
    #         if downsample < rate:
    #             if i == 0:
    #                 return i
    #             else:
    #                 return i-1
    #     return i
    def get_best_level_for_downsample(self, downsample):
        """Return the best level for displaying the given downsample."""
        i = self.level_downsamples.index(downsample)
        return max(0, i-1)

    def read_region(self, location, level, size):
        """Return a PIL.Image containing the contents of the region.
                location: (x, y) tuple giving the top left pixel in the level 0
                          reference frame.
                level:    the level number.
                size:     (width, height) tuple giving the region size.
        """
        pBuffer = POINTER(c_ubyte)()
        DataLength = c_int()
        tag = self.GetImageDataRoiFunc(self.img_pointer, self.khiScanScale / 2 ** level,
                                       location[0] // 2 ** level, location[1] // 2 ** level,
                                       size[0], size[1], byref(pBuffer), byref(DataLength), True)
        data = np.ctypeslib.as_array(
            (ctypes.c_ubyte * DataLength.value).from_address(ctypes.addressof(pBuffer.contents)))
        return Image.open(io.BytesIO(data))

    def get_image_block(self, c_scale, c_x, c_y, nWidth, nHeight):
        '''
        提取所在位置的图块文件流
        :param c_scale: 所使用倍镜数
        :param c_x: 中心x坐标
        :param c_y: 中心y坐标
        :param nWidth: 图块的宽
        :param nHeight: 图块的高
        :return: 图块的文件流，保存它就成为JPG文件
        '''
        pBuffer = POINTER(c_ubyte)()
        DataLength = c_int()
        '''
        bool GetImageDataRoiFunc( ImageInfoStruct sImageInfo, float fScale, 
        int sp_x, int sp_y, int nWidth, int nHeight,
        BYTE** pBuffer, int&DataLength, bool flag);

        参数：
        1.	sImageInfo：传入图像数据指针
        2.	fScale：传入倍率
        3.	sp_x：左上角X坐标
        4.	sp_y：右上角Y坐标
        5.	nWidth：宽度
        6.	nHeight：高度
        7.	pBuffer：返回图像数据指针
        8.	DataLength：返回图像字节长度
        9.	flag：true

        '''
        #从中心坐标移动到左上角坐标
        sp_x = c_x - (nWidth >> 1)
        sp_y = c_y - (nHeight >> 1)

        tag = self.GetImageDataRoiFunc(self.img_pointer, c_scale, sp_x, sp_y, nWidth, nHeight, byref(pBuffer),
                                       byref(DataLength), True)
        data = np.ctypeslib.as_array(
            (ctypes.c_ubyte * DataLength.value).from_address(ctypes.addressof(pBuffer.contents)))
        return Image.open(io.BytesIO(data))

    def get_thumbnail(self, scale):
        ImageWidth, ImageHeight = self.get_image_width_height_byScale(scale)
        return self.get_image_block(scale, ImageWidth>>1, ImageHeight>>1, ImageWidth, ImageHeight)

    ############################## v.03 代码 #################################
    '''
    关于标注的说明：
    1. 使用 FigureType="Polygon" 的曲线来进行区域边界的标记
    2. 不同的 Color属性来区分 良恶性区域（绿色对应良性，蓝色对应恶性（包括原位）, 黄色对应淋巴细胞区域））
    3. 每个区域用一段封闭曲线进行标注。
    4. 绿色区域标注在癌变区域内正常区域，黄色区域标注在正常区域内的淋巴细胞区域。
    '''
    # def read_annotation(self):
    #     '''
    #     读取标注文件
    #     :return:
    #     '''
    #     self.ano = {"TUMOR":[], "NORMAL":[], "LYMPH":[]}
    #
    #     # 使用minidom解析器打开 XML 文档
    #     fp = open(self.filename, 'r', encoding="utf-8")
    #     content = fp.read()
    #     fp.close()
    #
    #     content = content.replace('encoding="gb2312"', 'encoding="UTF-8"')
    #
    #     DOMTree = xml.dom.minidom.parseString(content)
    #     collection = DOMTree.documentElement
    #
    #     Regions = collection.getElementsByTagName("Region")
    #
    #     for Region in Regions:
    #         if Region.hasAttribute("FigureType"):
    #             if Region.getAttribute("FigureType") == "Polygon" :
    #                 Vertices = Region.getElementsByTagName("Vertice")
    #                 range_type = int(Region.getAttribute("Color"))
    #                 # contour_id = Region.getAttribute("Detail")
    #
    #                 posArray = np.zeros((len(Vertices), 2))
    #                 i = 0
    #                 for item in Vertices:
    #                     posArray[i][0] = float(item.getAttribute("X"))
    #                     posArray[i][1] = float(item.getAttribute("Y"))
    #                     i += 1
    #
    #                 # if range_type == TUMOR_RANGE_COLOR:
    #                 #     self.ano["TUMOR"].append(posArray)
    #                 # elif range_type == NORMAL_RANGE_COLOR:
    #                 #     self.ano["NORMAL"].append(posArray)
    #                 # elif range_type == LYMPH_RANGE_COLOR:
    #                 #     self.ano["LYMPH"].append(posArray)
    #                 if range_type == TUMOR_RANGE_COLOR:
    #                     self.ano["TUMOR"].append(posArray)
    #                 else:
    #                     self.ano["NORMAL"].append(posArray)
    #
    #     return
    # def create_mask_image(self, scale, edge_width):
    #     """
    #     在设定的倍镜下，生成四种标注区的mask图像（NECL）
    #     :param edge_width: 边缘区单边宽度
    #     :return: 对应的Mask图像
    #     """
    #     w, h = self.get_image_width_height_byScale(scale)
    #     '''
    #     癌变区代号 C， ano_TUMOR，将对应的标记区域，再腐蚀width宽。考虑嵌套的情况
    #     正常间质区代号 S， ano_NORMAL，将对应的标记区域，再腐蚀width宽。不考虑嵌套的情况
    #     边缘区代号 E， 在C和N，L之间的一定宽度的边缘，= ALL(有效区域) - C
    #    '''
    #     img = np.zeros((h, w), dtype=np.bool)
    #
    #     for contour in self.ano["TUMOR"]:
    #         tumor_range = np.rint(contour * scale).astype(np.int)
    #         rr, cc = draw.polygon(tumor_range[:, 1], tumor_range[:, 0])
    #         img[rr, cc] = ~ img[rr, cc]
    #
    #     for contour in self.ano["NORMAL"]:
    #         tumor_range = np.rint(contour * scale).astype(np.int)
    #         rr, cc = draw.polygon(tumor_range[:, 1], tumor_range[:, 0])
    #         img[rr, cc] = 0
    #         # img[rr, cc] = ~ img[rr, cc] # 因为Normal可能出现在Tumor区域中，而且Normal中又存在小区域的Tumor
    #
    #     C_img = img
    #     N_img = ~img
    #
    #     if edge_width > 1:
    #         C_inner = morphology.binary_erosion(img, selem=square(2 * edge_width))
    #         EI_img = np.bitwise_xor(C_inner, C_img)
    #         EI_img = morphology.binary_erosion(EI_img, selem=square(edge_width))
    #
    #         C_outer = morphology.binary_dilation(img, selem=square(2 * edge_width))
    #         EO_img = np.bitwise_xor(C_outer, C_img)
    #         EO_img = morphology.binary_erosion(EO_img, selem=square(edge_width))
    #
    #         common_E = np.bitwise_or(EO_img, EI_img)
    #         EI_img = np.bitwise_and(common_E, C_img)
    #         EO_img = np.bitwise_and(common_E, N_img)
    #     else:
    #         EI_img = np.zeros((h, w), dtype=np.bool)
    #         EO_img = np.zeros((h, w), dtype=np.bool)
    #
    #     return {"C": C_img, "N": N_img, "EI": EI_img, "EO": EO_img}


if __name__ == "__main__":
    kfb = KFB_Slide(r"E:\PAIP2020\HCH\HCH-MSI-H\1601453-11#.kfb")
    print(kfb.open_slide())